package org.ainlolcat.ainscience.statistics.handlers.hitran;

import org.ainlolcat.ainscience.utils.ArraysUtil;
import org.ainlolcat.ainscience.statistics.handlers.hitran.instfunc.InstrumentalFunction;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;

/**
 * @author ainlolcat
 *         Date: 5/1/13
 */
public strictfp class SpectrTemperatureFinder {
    //contains dispersion for different instrumental functions for different T
    static Hashtable<InstrumentalFunction,Hashtable<Double, Double>> dispersion = new Hashtable<InstrumentalFunction, Hashtable<Double, Double>>();

    protected Map<Double, Double> shifts = new Hashtable<Double, Double>();
    double T;

    double spectrZBD = Double.NaN;

    /**
     * difference in 1280 and 763 line is spectr. Logic of temperature search the same
     * @param T
     * @param func
     * @param x
     * @param y
     * @return
     * @throws java.io.IOException
     */
    protected double[] getSpectr(double T, InstrumentalFunction func,double[] x, double[] y) throws IOException {
        SpectrGenerator sg = new SpectrGenerator();
        double[] result = sg.getBxSpectr(T,func, x ,y);
        shifts.put(T, sg.getShift());
        return result;
    }

    public double getT(double[] x, double[] y, InstrumentalFunction func, double start, double end, double step) throws IOException {
        if (HitranConsts.USE_CONVERGENCE)
            return getTWithConvergence(x, y, func, start, end, step);
        else
            return getTInternal(x, y, func, start, end, step);
    }

    public double getTWithConvergence(double[] x, double[] y, InstrumentalFunction func, double start, double end, double step) throws IOException {
        double minDif = getDifference(x,y,start,func);
        T = start;
        int conv = 4;

        double _start = start;
        double _end = end;
        double _step = (_end - _start) / conv;
        while (true){
            T = getTInternal(x, y, func, _start, _end, _step);
            if (_step < step)
                break;
            _start = T - _step;
            _end = T + _step;
            _step = (_end - _start) / conv;
        }
        return T;
    }

    private double getTInternal(double[] x, double[] y, InstrumentalFunction func, double start, double end, double step) throws IOException {
        double minDif = getDifference(x,y,start,func);
        T = start;

        for (double t = start+step;t<=end;t+=step){
            double dif = getDifference(x,y,t,func);
            if (dif < minDif){
                minDif = dif; T = t;
            }
        }
        return T;
    }

    public double getShift()
    {
        return shifts.get(T);
    }

    private double getDifference(double[] x, double[] y,double T, InstrumentalFunction func) throws IOException {
        //return getDifferenceBySKO(x,y,T,func);
        return getDifferenceBySKOWithDer(x,y,T,func);
        //return getDifferenceByDisp(x,y,T,func);
    }

    private double getDifferenceByDisp(double[] x, double[] y,double T, InstrumentalFunction func) throws IOException {
        if (Double.isNaN(spectrZBD)){
            spectrZBD = getZeroBasedDispersion(x,y);
        }
        if (dispersion.containsKey(func)){
            Hashtable<Double, Double> dispByT = dispersion.get(func);
            if (!dispByT.containsKey(new Double(T))){
                dispByT.put(new Double(T), new Double(getZeroBasedDispersion(x,getSpectr(T,func, x,y))));
            }
            System.out.println(T + "\t" + (spectrZBD - dispByT.get(new Double(T))));
            return Math.abs(spectrZBD - dispByT.get(new Double(T)));
        }
        else{
            Hashtable<Double, Double> dispByT = new Hashtable<Double, Double>();
            dispByT.put(new Double(T), new Double(getZeroBasedDispersion(x,getSpectr(T,func, x,y))));
            dispersion.put(func,dispByT);
            System.out.println(T + "\t" + (spectrZBD - dispByT.get(new Double(T))));
            return Math.abs(spectrZBD - dispByT.get(new Double(T)));
        }
    }

    private double getDifferenceBySKO(double[] x, double[] y,double T, InstrumentalFunction func) throws IOException {
        double[] generated = getSpectr(T,func, x,y);
        double difference = 0;
        double yMax = ArraysUtil.getMax(y);
        double gMax = ArraysUtil.getMax(generated);
        for (int i=0;i<x.length;i++){
                //added weight
                difference += 100*Math.abs(y[i]/yMax)*Math.pow( y[i]/yMax - generated[i]/gMax     , 2 );
        }
        return difference;
    }

    private double getDifferenceBySKOWithDer(double[] x, double[] y,double T, InstrumentalFunction func) throws IOException {
        double[] generated = getSpectr(T,func, x, y);
        double difference = 0;
        double yMax = ArraysUtil.getMax(y);
        double gMax = ArraysUtil.getMax(generated);
        for (int i=1;i<x.length-1;i++){
            //added weight
            double cos = Math.abs(x[i+1]-x[i-1])/Math.sqrt(Math.pow(y[i+1]-y[i-1],2) + Math.pow(x[i+1]-x[i-1],2) );
            double normY = Math.abs(y[i]/yMax);
            double normG = Math.abs(generated[i]/gMax);
            difference += cos * normY  * Math.pow( normY - normG, 2 );
        }
        return difference;
    }

    private double getZeroBasedDispersion(double[] x, double[] y){
        double mean = 0;
        double ySumm = 0;
        for (int i=0;i<( x.length < y.length ? x.length : y.length);i++){
            ySumm+=y[i];
            mean += y[i]*x[i];
        }
        mean = mean / ySumm;

        double disp = 0;
        for (int i=0;i<(x.length<y.length?x.length:y.length);i++){
            double zbx = x[i] - mean;
            //it's not dispersion as You can see. It's just some difference in dispersion =)
            disp += y[i]*Math.abs(zbx)*zbx;
        }
        disp = disp / ySumm;
        return disp;
    }


}
